from typing import Dict

from sanic.request import Request

from app.exceptions import InvalidCode, UserExist
from users import redis_repos as user_redis_repo


async def register(request: Request) -> Dict:
    """
    注册后直接登录

    0. 临时生成一个验证码到Redis中, 5分钟有效期 TODO (正式环境记得去掉，或者做成邀请码注册)
    1. 判断验证码是否正确(放步骤2之前是为了减少数据库访问)
    2. 判断用户是否存在(步骤1 2合并)
        1. 先判断Redis中是否存在
        2. 再分布式锁判断是否有其他人在注册这个登录名，锁30秒
        3. Redis中不存在再判断数据库中是否存在，减少数据库访问

    3. 加密密码并创建用户
    4. 生成Token并保存(保存是为了防止Redis挂了)
    5. finally 释放步骤2.2 分布式锁
    5. 刷新Redis缓存
        1. 手机是否注册，并对应用户ID
        2. 邮箱是否存在，并对应用户ID
        3. 用户ID对应用户
    """
    request_body = request.json
    phone = request_body.get("phone")
    password = request_body.get("password")
    code = request_body.get("code")

    # 0. 临时生成一个验证码到Redis中, 5分钟有效期 TODO (正式环境记得去掉，或者做成邀请码注册)
    await user_redis_repo.set_phone_register_verify_code(request.app.redis, phone, "0000")

    # 1. 判断验证码是否正确(放步骤2之前是为了减少数据库访问)
    value = await user_redis_repo.get_phone_register_verify_code(request.app.redis, phone)
    if code != value:
        raise InvalidCode()

    try:
        # 2. 判断用户是否存在(1. 先判断Redis中是否存在，2. Redis中不存在再判断数据库中是否存在，减少数据库访问)
        # 2.1 先判断Redis中是否存在
        # user_id = await user_redis_repo.get_phone_to_user_id(request.app.redis, phone)
        # if user_id:
        #     raise UserExist()
        # 2.2. 再分布式锁判断是否有其他人在注册这个登录名
        phone_lock = await user_redis_repo.set_phone_to_user_id(
            request.app.redis,
            phone,
            user_id=0,
            exist=request.app.redis.SET_IF_NOT_EXIST,
            expire=30   # 最多锁30秒
        )
        if not phone_lock:
            print("phone_lock: ", phone_lock)
            raise UserExist()

        # 2.3. Redis中不存在再判断数据库中是否存在，减少数据库访问
        user = user_db_repo.get_user_by_phone(phone)
        if user:
            raise UserExist()

        # 3. 加密密码并创建用户
        hashed_password = sha256(password)
        user = User(phone=phone, password=hashed_password)
        user.update(commit=True)

        # 4. 生成Token并保存(保存是为了防止Redis挂了，数据库先顶一会儿)
        token: str = generate_token({"user_id": user.id}, secret=settings.JWT_SECRET_KEY, ttl=settings.JWT_TTL)
        user.update(commit=True, **{"token": token})

    finally:
        # 清除该分布式锁缓存缓存，如果不出错，下方会重新添加该缓存
        await user_redis_repo.delete_phone_to_user_id(request.app.redis, phone)

    # 5. 刷新Redis缓存
    await user_redis_repo.flush_user_cache(
        redis=request.app.redis,
        redis_bytes=request.app.redis_bytes,
        user=user
    )
    return user.to_my_dict()


@transactional
async def login(request: Request) -> Dict:
    """
    登录

    1. 判断用户的用户名密码正确
    2. 生成Token并保存(保存是为了防止Redis挂了)
    3. 刷新Redis缓存
    """
    request_body = request.json
    phone = request_body.get("phone")
    password = request_body.get("password")

    # 1. 判断用户的用户名密码正确(从Redis/数据库中拿用户信息)
    user_id = await user_redis_repo.get_phone_to_user_id(request.app.redis, phone)
    if user_id:     # 先从Redis中查询
        user: User = await user_redis_repo.get_user_id_to_user(request.app.redis_bytes, user_id)
    if not user:
        user: User = user_db_repo.get_user_by_phone(phone)  # Redis中没有查到才去数据库中查
    if not user:
        raise UserNotExist()
    user.authenticate(password)  # 认证是否密码正确

    # 2. 生成Token并保存(保存是为了防止Redis挂了)
    token: str = generate_token({"user_id": user.id}, secret=settings.JWT_SECRET_KEY, ttl=settings.JWT_TTL)
    user.update(commit=True, **{"token": token})

    # 3. 刷新Redis缓存
    await user_redis_repo.flush_user_cache(
        redis=request.app.redis,
        redis_bytes=request.app.redis_bytes,
        user=user
    )
    return user.to_my_dict()


async def profile(request: Request) -> Dict:
    """
    获取用户信息
    """
    user: User = request.ctx.user
    return user.to_my_dict()